import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

import jssc.SerialPort;
import jssc.SerialPortException;

import org.json.JSONException;
import org.json.JSONObject;

//** Classe associée à chaque client **
//** Il y aura autant d'instances de cette classe que de clients connectés **
//implémentation de l'interface Runnable (une des 2 méthodes pour créer un thread)
class ClientThread implements Runnable {
	private Thread _t; // contiendra le thread du client
	private Socket _s; // recevra le socket liant au client
	private PrintWriter _out; // pour gestion du flux de sortie
	private BufferedReader _in; // pour gestion du flux d'entrée
	private Serveur _serv; // pour utilisation des méthodes de la classe
							// principale
	private int _numClient = 0; // contiendra le numéro de client géré par ce
								// thread

	// ** Constructeur : crée les éléments nécessaires au dialogue avec le
	// client **
	ClientThread(Socket s, Serveur serv) throws SerialPortException {
		_serv = serv;
		_s = s;
		try {
			// fabrication d'une variable permettant l'utilisation du flux de
			// sortie avec des string
			_out = new PrintWriter(_s.getOutputStream());
			// fabrication d'une variable permettant l'utilisation du flux
			// d'entrée avec des string
			_in = new BufferedReader(new InputStreamReader(_s.getInputStream()));
			// ajoute le flux de sortie dans la liste et récupération de son
			// numero
			// _numClient = serv.addClient(_out);
		} catch (IOException e) {
		}

		_t = new Thread(this); // instanciation du thread
		_t.start();
	}

	// ** Methode : exécutée au lancement du thread par t.start() **
	// ** Elle attend les messages en provenance du serveur et les redirige **
	// cette méthode doit obligatoirement être implémentée à cause de
	// l'interface Runnable
	public void run() {
		// on indique dans la console la connection d'un nouveau client
		System.out
				.println("Un nouveau client s'est connecte, no " + _numClient);
		try {
			String messageString;
			// Reception message
			while ((messageString = _in.readLine()) != null) {

				JSONObject messageRecu = new JSONObject(messageString);
				System.out.println("JSON : " + messageRecu);

				String entite = (String) messageRecu.get("entity");
				System.out.println("Entité :" + entite);

				switch (Integer.parseInt(entite)) {
				case 0:
					System.out.println("LectureMessageClient");
					lecureMessageCLient(messageRecu);
					break;
				case 1:
					lecureMessageVelo(messageRecu);
					break;
				}

			}
		} catch (Exception e) {
		} finally // finally se produira le plus souvent lors de la deconnexion
					// du client
		{
			try {
				// on indique à la console la deconnexion du client
				System.out.println("Le client no " + _numClient
						+ " s'est deconnecte");
				//_serv.delClient(_numClient); // on supprime le client de la
												// liste
				_s.close(); // fermeture du socket si il ne l'a pas déjà été (à
							// cause de l'exception levée plus haut)
			} catch (IOException e) {
			}
		}
	}

	private void lecureMessageVelo(JSONObject messageRecu) {
		try {
			String idVelo = (String) messageRecu.get("idVelo");
			System.out.println("idVelo : " + idVelo);
			EntiteVelo veloBoutonAppuye = _serv.getVeloById(Integer
					.parseInt(idVelo));

			if (veloBoutonAppuye.getEtat() == Etat.PreOccupe) {
				synchronized (veloBoutonAppuye) {
					veloBoutonAppuye.setEtat(Etat.Occupe);
					veloBoutonAppuye.notify();
					_out.println("0");
					_out.flush();
				}
			} else if (veloBoutonAppuye.getEtat() == Etat.PreRendu) {
				synchronized (veloBoutonAppuye) {
					veloBoutonAppuye.setEtat(Etat.PreLibre);
					veloBoutonAppuye.notify();
					if (veloBoutonAppuye.getProblem() == 1) {
						_out.println("2");
						_out.flush();
					} else {
						_out.println("1");
						_out.flush();
					}
				}				
			} else {
				// Aucune demande de location pour ce vélo
				_out.println("3");
				_out.flush();
			}

		} catch (JSONException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	private void lecureMessageCLient(JSONObject messageRecu)
			throws SerialPortException, InterruptedException, IOException {
		try {
			String opType = (String) messageRecu.get("opType");
			System.out.println("Optype : " + opType);
			switch (Integer.parseInt(opType)) {
			case 4:
				connexionClient(messageRecu);
				break;
			case 0:
				locationVeloClient(messageRecu);
				break;
			case 1:
				renduVeloClient(messageRecu);
				break;
			case 3:
				problemeVeloClient(messageRecu);
				break;
			case 2:
				localisationVeloDispo(messageRecu);
				break;
			}
		} catch (JSONException e) {
			e.printStackTrace();
		}

	}

	private void localisationVeloDispo(JSONObject messageRecu) {
		// TODO Auto-generated method stub

	}

	private void problemeVeloClient(JSONObject messageRecu)
			throws SerialPortException {
		// � finir

		try {
			String username = messageRecu.getString("username");

			EntiteClient client = _serv.getClientByName(username);
			System.out.println("client: " + client.getLogin());
			EntiteVelo veloSouhaite = _serv.getVeloById(Integer
					.parseInt(messageRecu.getString("idVelo")));
			System.out.println("velo: " + veloSouhaite.getId());

			// traiter message et type
			System.out.println("OK signalation recu");
			// Envoie 0
			_out.println("0");
			_out.flush();

			veloSouhaite.setProblem(1);

		} catch (JSONException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	private void renduVeloClient(JSONObject messageRecu)
			throws SerialPortException, IOException {
		try {
			String username = messageRecu.getString("username");
			String idVelo = messageRecu.getString("idVelo");
			EntiteClient client = _serv.getClientByName(username);
			EntiteVelo veloARendre = _serv
					.getVeloById(Integer.parseInt(idVelo));

			Facture facture = client.getFactures().get(
					client.getFactures().size() - 1);
			
			
			if (veloARendre != null && veloARendre.getEtat() == Etat.Reserve
					&& facture.getVelo().getId() == veloARendre.getId()) {

				// Envoie 0
				_out.println("0");
				_out.flush();
				
				synchronized (veloARendre) {
					System.out.println("[CLIENT] Mise en attente pour "
							+ username);
					veloARendre.setEtat(Etat.PreRendu);
					veloARendre.wait(10000);
					System.out.println("[CLIENT] Libération " + username);
				}

				if (veloARendre.getEtat() == Etat.PreLibre) {
					System.out.println("OK wait image");
					// Envoie 0
					_out.println("0");
					_out.flush();

					// getImage
					while (_in.readLine() == null)
						;

					facture.setEndTime(System.currentTimeMillis());
					long prix = (long) (0.0001 * (float) (facture.getEndTime() - facture
							.getBeginingTime()));

					System.out.println("OK image recu");
					// send ok
					_out.println(String.valueOf(prix));
					_out.flush();
					
					if (veloARendre.getProblem() == 1) {
						veloARendre.setProblem(0);
						veloARendre.setEtat(Etat.Immobilise);
					} else {
						veloARendre.setEtat(Etat.Libre);
					}
							
				} else {
					// send 1
					_out.println("1");
					_out.flush();
				}

			} else {
				// send 1
				_out.println("1");
				_out.flush();
			}
		} catch (JSONException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	private void locationVeloClient(JSONObject messageRecu)
			throws SerialPortException, InterruptedException {
		try {
			String username = messageRecu.getString("username");
			EntiteClient client = _serv.getClientByName(username);
			EntiteVelo veloSouhaite = _serv.getVeloById(Integer
					.parseInt(messageRecu.getString("idVelo")));

			if (preOccupeVelo(veloSouhaite)) {
				// Envoie 0
				_out.println("0");
				_out.flush();

				synchronized (veloSouhaite) {
					System.out.println("[CLIENT] Mise en attente pour "
							+ username);
					veloSouhaite.wait(10000);
					System.out.println("[CLIENT] Libération " + username);
				}

				if (veloSouhaite.getEtat() == Etat.Occupe) {
					Facture facture1 = new Facture(veloSouhaite,
							System.currentTimeMillis(), client);
					client.addFacture(facture1);

					veloSouhaite.setEtat(Etat.Reserve);
					_out.println("0");
					_out.flush();
				} else {
					// Envoie 1
					_out.println("1");
					_out.flush();
				}
			} else {
				// Envoie 1
				_out.println("1");
				_out.flush();
			}
		} catch (JSONException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	private synchronized boolean preOccupeVelo(EntiteVelo veloSouhaite) {
		if (veloSouhaite != null && veloSouhaite.getEtat() == Etat.Libre) {
			veloSouhaite.setEtat(Etat.PreOccupe);
			return true;
		}
		return false;
	}

	private void connexionClient(JSONObject messageRecu) {
		try {
			EntiteClient clientConnecte = _serv.getClientByName(messageRecu
					.getString("username"));

			if (clientConnecte != null
					&& clientConnecte.verifyPassword(messageRecu
							.getString("password")) == 0) {
				// Envoie 0
				_out.println("0");
				_out.flush();
			} else {
				// Envoie 1 : Problème
				_out.println("1");
				_out.flush();
			}
		} catch (JSONException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
}